#include <dinput.h>
#include <qmessagebox.h>
#include <fstream>
#include "Joystick.h"

int Joystick::DIEnumDeviceObjectsCallback(LPCDIDEVICEOBJECTINSTANCE lpddoi, void *thisPtr) 
{
	std::wstring test(lpddoi->tszName);

	if (test.find(L"Collection") != -1)
		return DIENUM_STOP;

	Joystick *_this = (Joystick *)thisPtr;
	_this->_bufferSize = max(_this->_bufferSize, lpddoi->dwOfs + 1);
	_this->_inputVector.append(new Input(lpddoi->dwOfs, lpddoi->dwType, lpddoi->dwFlags, lpddoi->guidType));
	return DIENUM_CONTINUE;
}

Joystick::Joystick(IDirectInputDevice8 *dev)
{
	_dev = dev;
	_bufferSize = 0;
	_currState = 0;
	_prevState = 0;
}

Joystick::~Joystick(void)
{
	for (int i = 0; i < _inputVector.size(); i++) {
		Input *input = _inputVector.at(i);
		delete input;
	}
	delete _currState;
	delete _prevState;
	_dev->Unacquire();
	_dev->Release();
}

void Joystick::setupDevice() 
{
	HRESULT hr;

	hr = _dev->EnumObjects((LPDIENUMDEVICEOBJECTSCALLBACK)Joystick::DIEnumDeviceObjectsCallback, this, DIDFT_ALL);
	if (FAILED(hr))
		throw JoystickSetupException(hr);

	// Allocate our input buffers. 
	_bufferSize += 4 - (_bufferSize % 4);
	_currState = new char[_bufferSize]; // Must be multiple of DWORD in size.
	memset(_currState, 0x00, _bufferSize);
	_prevState = new char[_bufferSize];
	memset(_currState, 0x00, _bufferSize);

	// create format objects for all of the input devices we want to recieve data from
	size_t allocByteCount = _inputVector.size() * sizeof(DIOBJECTDATAFORMAT);
	_devObjectFormat = (LPDIOBJECTDATAFORMAT)new char[allocByteCount];
	for (int i = 0; i < _inputVector.size(); i++) {
		LPCDIOBJECTDATAFORMAT obfFmt = _inputVector.at(i)->getObjectFmt();
		char *destPtr = (char *)_devObjectFormat + (i * sizeof(DIOBJECTDATAFORMAT));
		memcpy(destPtr, obfFmt, sizeof(DIOBJECTDATAFORMAT));
	}

	// create data format
	_dataFormat.dwSize = sizeof(DIDATAFORMAT);
	_dataFormat.dwObjSize = sizeof(DIOBJECTDATAFORMAT);
	_dataFormat.dwFlags = DIDF_ABSAXIS;
	_dataFormat.dwDataSize = _bufferSize;
	_dataFormat.dwNumObjs = _inputVector.size();
	_dataFormat.rgodf = _devObjectFormat;

	// will return E_HANDLE, but will still operate.
	// not associating the device object with a window
	// handle. 
	hr = _dev->SetCooperativeLevel(NULL, DISCL_BACKGROUND|DISCL_EXCLUSIVE);

	hr = _dev->SetDataFormat(&_dataFormat);
	if (FAILED(hr)) 
		throw JoystickSetupException(hr);

	hr = _dev->Acquire();
	while (hr == DIERR_INPUTLOST) {
		hr = _dev->Acquire();
    }

	if (FAILED(hr)) 
		throw JoystickSetupException(hr);

}

void Joystick::poll() 
{
	HRESULT hr;

    hr = _dev->Poll(); 
    if (FAILED(hr)) {
        hr = _dev->Acquire();
        while (hr == DIERR_INPUTLOST) {
            hr = _dev->Acquire();
        }

        if ((hr == DIERR_INVALIDPARAM) || (hr == DIERR_NOTINITIALIZED)) {
            throw JoystickPollingException(hr);
        }
    }

    if (FAILED(hr = _dev->GetDeviceState(_bufferSize, _currState))) {
        throw JoystickPollingException(hr);
    }

	for (int i = 0; i < _inputVector.size(); i++) {
		int type = DIDFT_GETTYPE(_inputVector.at(i)->getType());
		int offset = _inputVector.at(i)->getOffset();
		
		if ((type == DIDFT_BUTTON) || (type == DIDFT_TGLBUTTON) || (type == DIDFT_PSHBUTTON)) {
			
			if (_prevState[offset] != _currState[offset]) {
				
				if (_currState[offset] &0x80) {
					emit keyDown(i);
				} else {
					emit keyUp(i);
				}
			}
		} else if ((type == DIDFT_AXIS) || (type == DIDFT_ABSAXIS) || (type == DIDFT_RELAXIS)) {
			
			char *p = _prevState + offset;
			char *c = _currState + offset;
			int prev = *(int *)p;
			int curr = *(int *)c;

			if (prev != curr) 
				emit axisChanged(i, curr);
		} else if (type == DIDFT_POV) {

			char *p = _prevState + offset;
			char *c = _currState + offset;
			int prev = *(int *)p;
			int curr = *(int *)c;

			if (prev != curr) {
				if (((curr & 0xffff) || (curr == -1)) && !(prev % 9000)) {
					emit keyUp(_inputVector.size() + (prev / 9000));
				} else if (!(prev % 9000)) {
					if ((curr % 9000)) {
						emit keyUp(_inputVector.size() + (prev / 9000));
					} else {
						emit keyDown(_inputVector.size() + (curr / 9000));
						emit keyUp(_inputVector.size() + (prev / 9000));
					}
				} else if (!(curr % 9000)) {
					emit keyDown(_inputVector.size() + (curr / 9000));
				}
			}
		}
	}

	memcpy(_prevState, _currState, _bufferSize);
}
